/**
 * Created by haua on 2015/6/27.
 * 此事件是为jQuery添加的
 * 等待完善：双击监听，长按监听
 */
(function(jQuery, undefined){
    var add = jQuery.event.add,
        remove = jQuery.event.remove,

        //不知为何，直接传data进jQuery.event.trigger 会失败...
        trigger = function(node,type,data){
            var event = jQuery.Event(type);
            jQuery.event.trigger($.extend(event,data),null,node,true);
        };

    function moveend(e){
        var w, h, event;
        w = e.currentTarget.offsetWidth;
        h = e.currentTarget.offsetHeight;

        //console.log('moveend');
        // Copy over some useful properties from the move event
        event = {
            distX: e.distX,
            distY: e.distY,
            velocityX: e.velocityX,
            velocityY: e.velocityY,
            finger: e.finger
        };

        // Find out which of the four directions was swiped
        if(e.distX > e.distY){
            if(e.distX > -e.distY){
                if(e.distX/w > settings.threshold || e.velocityX * e.distX/w * settings.sensitivity > 1){
                    event.type = 'swiperight';
                    trigger(e.currentTarget, event);
                }
            }else{
                if(-e.distY/h > settings.threshold || e.velocityY * e.distY/w * settings.sensitivity > 1){
                    event.type = 'swipeup';
                    trigger(e.currentTarget, event);
                }
            }
        }else{
            if(e.distX > -e.distY){
                if(e.distY/h > settings.threshold || e.velocityY * e.distY/w * settings.sensitivity > 1){
                    event.type = 'swipedown';
                    trigger(e.currentTarget, event);
                }
            }else{
                if(-e.distX/w > settings.threshold || e.velocityX * e.distX/w * settings.sensitivity > 1){
                    event.type = 'swipeleft';
                    trigger(e.currentTarget, event);
                }
            }
        }
    }

    /**
     * 给指定Dom添加一个数据
     * @param node Dom
     * @param key string 数据的key名
     * @return mix 返回数据，如果没有则返回false
     * */
    function getData(node,key){
        //var data = jQuery.data(node, 'hhh_event');
        var data = $(node).data('hhh_event');
        return data&&data[key];
    }
    function setData(node,key,value){
        var data = $(node).data('hhh_event')||{};
        data[key] = typeof value=='undefined'?0:value;
        $(node).data('hhh_event',data);
        return true;
    }

    /**
     * 返回注册jq新事件的对象
     * */
    function getEventObj(eventName,fn){
        return {
            /**
             * 初始化事件处理器 - this指向DOM元素
             * @param data 附加的数据
             * @param namespaces 事件类型命名空间
             * @param eventHandle 回调函数
             */
            setup:function(data,namespaces,eventHandle){
                fn(this);
                return true;
            },
            /**
             * 卸载事件处理器 - this指向元素
             * @param namespaces 事件类型命名空间
             */
            teardown: function(namespaces){
                var data = getData(this);

                // If another swipe event is still setup, don't teardown.
                if (!data||--data.count > 0) { return; }

                remove(this, 'moveend', moveend);

                return true;
            }
        }
    }

    //清除指定数组中的所有timeout
    function clearTimeoutEvents(events){
        var i = 0,tNum = events.length;
        while(events.length&&i<tNum){
            clearTimeout(events.shift())
        }
        return events;
    }

    /**
     * swipe和tap事件 =================================================
     * 尽量不要用tap事件，因为如果一个点击位置同时有DOM侦听tap事件又有DOM侦听click事件的话，tap事件执行完之后会执行click事件的
     * 这样会造成点击穿透的bug发生。
     * */
    (function(){
        var winWidth = $(window).width(),
            defSetting = {
                // Ratio of distance over target finger must travel to be considered a swipe.
                //目标手指必须经过一段距离比才能被认为是手指滑动
                threshold: 0.04,
                // Faster fingers can travel shorter distances to be considered swipes. 'sensitivity' controls how much. Bigger is shorter.
                //移动速度快的手指可以经过短一点的距离就被认为是滑动。此值越大，需要经过的距离越短(功能未实现)
                sensitivity: 6,
                swipeThreshold:0 //
            };
        defSetting.swipeThreshold = winWidth*defSetting.threshold;

        jQuery.event.special.singletap = getEventObj('singletap',init);
        jQuery.event.special.doubletap = getEventObj('doubletap',init);
        jQuery.event.special.longtap = getEventObj('longtap',init);
        jQuery.event.special.swipe = getEventObj('swipe',init);
        jQuery.event.special.swipestart = getEventObj('swipestart',init);
        jQuery.event.special.swipeend = getEventObj('swipeend',init);
        jQuery.event.special.swipeleft = getEventObj('swipeleft',init);
        jQuery.event.special.swiperight = getEventObj('swiperight',init);
        jQuery.event.special.swipeup = getEventObj('swipeup',init);
        jQuery.event.special.swipedown = getEventObj('swipedown',init);

        /**
         * 判断swipe方向
         */
        function swipeDirection(x1, x2, y1, y2){
            return Math.abs(x1 - x2) >=	Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'left' : 'right') : (y1 - y2 > 0 ? 'up' : 'down')
        }
        /**
         * 监听原生的事件，主动触发模拟事件
         *
         */
        function init(DOM){
            var hasSwipe = getData(DOM,'hasSwipe');
            if(hasSwipe)return;
            setData(DOM,'hasSwipe',1);

            var touchStartTime = 0;//轻击开始时间

            var lastEventTime = 0;//记录上一次发生事件的时间

            var x1,y1,x2,y2;//记录开始和结束滑动的位置

            var lastEventData;//上一次swipe的事件数据，因为swipeend是没有松开点的位置数据的，这时使用上次的数据

            var isSwipe = false; //已经被定义为滑动事件，则不能变为其它事件了

            var touchDelay = [];//轻击事件的延时器

            var longTap = [];//测试长按事件的延时器

            var isActive = false;//记录当前事件是否已为等待结束的状态（swipe事件是否开始了）

            var eventMark = null;//记录有坐标信息的事件

            //断定此次事件为轻击事件
            function isSingleTap(){
                actionOver();
                trigger(DOM,'singletap',eventMark);
            }
            //触屏开始
            function touchStart(e){
                isSwipe = false;
                //缓存事件
                eventMark = e.originalEvent||e;
                x1 = eventMark.touches[0].pageX;
                y1 = eventMark.touches[0].pageY;
                x2 = 0;
                y2 = 0;
                isActive = true;
                touchStartTime = new Date();

                //trigger(this,'swipestart',e);

                //检测是否为长按
                clearTimeout(longTap);
                longTap.push(setTimeout(function(){
                    actionOver(e);
                    //断定此次事件为长按事件
                    trigger(this,'longtap',e);
                },500));
            }
            //触屏结束
            function touchend(e){
                if(isSwipe){
                    isSwipe = false;
                    var direction = swipeDirection(x1, x2, y1, y2),
                        eventData = {
                            originalEvent:eventMark,
                            startPosition:{pageX : x1,pageY : y1},
                            lastPosition:lastEventData.lastPosition,
                            nowPosition:{pageX : x2,pageY : y2},
                            direction:direction,
                            delta:countDelta()
                        };
                    trigger(DOM,'swipeend',eventData);//eventMark {startPosition:{pageX : x1,pageY : y1}}
                }

                if(!isActive){
                    return
                }
                var ifDoubleTap = getData(DOM,'doubletap');
                //若未监听doubleTap，直接响应
                if(!ifDoubleTap){
                    isSingleTap();
                }else if(delta > 200){ //双击监听未完善，不要用！
                    //延迟响应
                    touchDelay.push(setTimeout(isSingleTap,190));
                }else{
                    clearTimeoutEvents(touchDelay);
                    actionOver(e);
                    //断定此次事件为连续两次轻击事件
                    //EMIT.call(this_touch,'doubletap',eventMark);
                }
            }
            //计算两次调用此函数时的时间间隔
            function countDelta(){
                var now = new Date(),
                    delta = now - lastEventTime;
                lastEventTime = now;
                return delta;
            }
            //手指移动
            function touchmove(e){
                //e.preventDefault(); //没有这个的话，安卓的touchmove事件只触发一次的，但是有了这个之后，可滚动的元素的滚动也会被阻止了

                e = e.originalEvent || e;//如果这个e是jq封装过的，那这个才是原生的事件。

                eventMark = e;//缓存事件

                if(!isActive)
                    return;

                var lastPosition = {pageX : x2 || x1,pageY : y2 || x1}; //因为swipe事件是连续事件，每次变化发生时都保存好上一次发生的状态好一点
                x2 = e.touches[0].pageX;
                y2 = e.touches[0].pageY;

                //console.log(x1-x2);
                //return;
                if(Math.abs(x1-x2)>defSetting.swipeThreshold || Math.abs(y1-y2)>defSetting.swipeThreshold || isSwipe){
                    isSwipe = true;
                    //console.log('dadada');
                    var direction = swipeDirection(x1, x2, y1, y2),
                        eventData = {
                            originalEvent:e,
                            startPosition:{pageX : x1,pageY : y1},
                            lastPosition:lastPosition,
                            nowPosition:{pageX : x2,pageY : y2},
                            direction:direction,
                            delta:countDelta()
                        };
                    lastEventData = eventData;
                    //断定此次事件为移动事件
                    trigger(DOM,'swipe',eventData);

                    trigger(DOM,'swipe' + direction,eventData);
                }

                clearTimeoutEvents(longTap);
                clearTimeoutEvents(touchDelay);
            }
            //单次用户操作结束
            function actionOver(e){
                isActive = false;
                clearTimeoutEvents(longTap);
                clearTimeoutEvents(touchDelay);
            }

            /**
             * 对开始手势的监听
             */

            add(DOM,'touchstart',touchStart);
            //add(DOM,'MSPointerDown',touchStart);
            //add(DOM,'pointerdown',touchStart);

            /**
             * 对手势结束的监听（轻击）
             */
            add(DOM,'touchend',touchend);
            //add(DOM,'MSPointerUp',touchend);
            //add(DOM,'pointerup',touchend);

            /**
             * 对移动手势的监听
             */
            add(DOM,'touchmove',touchmove);
            //add(DOM,'MSPointerMove',touchmove);
            //add(DOM,'pointermove',touchmove);

            /**
             * 对移动结束的监听
             */
            add(DOM,'touchcancel',actionOver);
            //add(DOM,'MSPointerCancel',actionOver);
            //add(DOM,'pointercancel',actionOver);
        }
    }());


    /**
     * scroll事件 =================================================
     * 用于解决由于拖动而造成DOM滚动的事件侦听
     * dragscrollend 拖动滚动结束，这个事件在ios支持得不好，快速拖动松开手指，会闪一下，然后才
     * */
    (function(){
        var faultTolerant = 18; //滚动到底部的允许误差范围

        jQuery.event.special.dragscrollend = getEventObj('dragscrollend',scrollInit);
        jQuery.event.special.scrollbottom = getEventObj('scrollbottom',scrollBottomInit);

        function scrollInit(DOM){
            DOM = getCorrectDom(DOM);
            if(getData(DOM,'hhhScroll')){
                return;
            }
            setData(DOM,'hhhScroll',1);

            var waitTime = 108, //上次滚动事件发生之后，等待多久以确定不再滚动
                timeOuts = [],
                mouseDowned = false,
                mousedown = 'touchstart',
                mouseup = 'touchend';

            add(DOM,mousedown,function(){
                mouseDowned = true;
                clearTimeoutEvents(timeOuts);
            });
            add(DOM,mouseup,onmouseup);
            add(DOM,'scroll',onscroll);

            //松开手
            function onmouseup(e){
                mouseDowned = false;

                //万一手指松开了以后不再滚动了，也要发出事件
                timeoutEvent('mouseup');
            }
            //滚动时
            function onscroll(e){
                if(!mouseDowned){
                    timeoutEvent();
                }
            }
            //最后是靠这个setTimeout发出事件的
            function timeoutEvent(from){
                clearTimeoutEvents(timeOuts);
                timeOuts.push(setTimeout(function(){
                    clearTimeoutEvents(timeOuts);
                    //console.log(timeOuts);
                    if(!mouseDowned){ //timeout时间内，重新点击的话，就别动了
                        trigger(DOM,'dragscrollend');
                    }
                },waitTime+(from=='mouseup'?100:0))); //如果是手松开的事件，则等待久一点再触发
            }

        }

        function scrollBottomInit(dom){
            var correctDOM = getScrollDom(dom);
            judgeScrollEnd(null,correctDOM,dom);

            if(getData(correctDOM,'scrollbottom'))
                return;
            setData(correctDOM,'scrollbottom',1);

            add(correctDOM,'scroll',function(e){
                judgeScrollEnd(e,this,dom);
            });
        }

        /**
         * @param e
         * @param tar 真正监听scroll事件的DOM对象
         * @param oldTar 前端需要发出事件的DOM对象，因为body、document.documentElement都不会有scroll事件 ，所以实际不是监听它们的scroll
         * */
        function judgeScrollEnd(e,tar,oldTar){
            tar = isNode(tar)?tar : this;

            var tarJq = $(tar),
                tarHeight = tarJq.height(), //要的是显示区域的高度
                tarScrollTop = tarJq.scrollTop(),
                tarScrollHeight = tar.scrollHeight;

            if(tar==document || tar==window || tar.nodeName=='BODY'){
                //tar = document.documentElement;
                tarScrollHeight = document.documentElement.scrollHeight;//如果目标是document、window，它们可没有可滚动高度
                tarHeight = $(window).height(); //如果目标是document，它的高度可不是可视区域的高度。
            }
            if(tarScrollHeight<=tarScrollTop+tarHeight+faultTolerant){
                triggerScrollbottom(tar,oldTar);
            }
        }

        function isNode(x){
            return !!(x && x.nodeType);
        }

        //刚实例化这个事件的时候直接trigger是不行的，可能是因为这个事件还没实例化完成，要延迟一点执行才行
        function triggerScrollbottom(tar,oldTar){
            setTimeout(function(){
                trigger(oldTar,'scrollbottom',{more:function(){judgeScrollEnd(null,tar,oldTar)}})
            },89);
        }

        //返回具有scroll事件的DOM，body、document.documentElement都不会有scroll事件
        function getScrollDom(dom){
            if(dom==document || dom==window || dom.nodeName=='BODY'){
                return window;
            }
            return dom;
        }


        function getCorrectDom(dom){
            if($.inArray(dom,[document,document.body,document.documentElement])){
                return window;
            }
            return dom;
        }
    }());

    /**
     * 电脑拖拽事件
     * drag
     * */
    (function(){
        jQuery.event.special.drag = getEventObj('drag',dragInit);
        //DOM 被添加事件的dom
        function dragInit(DOM){
            if(getData(DOM,'hhhDrag')){return;}
            setData(DOM,'hhhDrag',1);

            var mouseIsDowm = false,
                startPosition,
                lastPosition;

            add(DOM,'mousedown',onMouseDown);
            add(DOM,'mousemove',onMouseMove);
            add(window,'mouseup',function(e){
                e.stopPropagation();
                mouseIsDowm = false;
            });

            function onMouseDown(e){
                mouseIsDowm = true;
                startPosition = {pageX : e.originalEvent.pageX,pageY : e.originalEvent.pageY};
                lastPosition = {pageX : e.originalEvent.pageX,pageY : e.originalEvent.pageY};
            }

            function onMouseMove(e){
                if(!mouseIsDowm) return;

                var oriE = e.originalEvent,
                    nowPosition = {pageX : oriE.pageX,pageY : oriE.pageY},
                    eventData = {
                    originalEvent:e,
                    startPosition:startPosition,
                    lastPosition:lastPosition,
                    nowPosition:nowPosition
                };
                lastPosition = nowPosition;

                trigger(DOM,'drag',eventData);
            }



        }
    }());
}(jQuery));